package com.hongtech.tiny.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.google.common.collect.Lists;
import com.hongtech.tiny.common.exception.CustomException;
import com.hongtech.tiny.common.service.RedisService;
import com.hongtech.tiny.common.service.RedisWatcherLockService;
import com.hongtech.tiny.common.service.impl.RedisServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;

/**
 * Redis配置类
 */
@EnableCaching
@Configuration
public class RedisConfig {

    private static final Logger logger = LoggerFactory.getLogger(RedisConfig.class);

    private static final String REDIS_MODE_CLUSTER = "cluster";

    @Value("${ht.redis.mode}")
    private String redisMode;

    @Value("${ht.redis.clusters}")
    private String clusterNodes;

    @Value("${ht.redis.host}")
    private String host;

    @Value("${ht.redis.port}")
    private String port;

    @Value("${ht.redis.database}")
    private String database;

    @Value("${ht.redis.password}")
    private String password;

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        logger.info("=============redis mode ============ {}", redisMode);
        JedisConnectionFactory jedisConnectionFactory;
        if (REDIS_MODE_CLUSTER.equals(redisMode)) {
            jedisConnectionFactory = getClusterFactory();
        } else {
            jedisConnectionFactory = getStandaloneFactory();
        }
        Object nativeConnection = jedisConnectionFactory.getConnection().getNativeConnection();
        logger.info("连接对象 {}", nativeConnection);
        return jedisConnectionFactory;
    }

    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        return new JedisPoolConfig();
    }

    /**
     * 获取redis单机模式连接
     */
    private JedisConnectionFactory getStandaloneFactory() {
        logger.info("=============getStandaloneFactory init============");
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(Integer.parseInt(port));
        if (StringUtils.isNotEmpty(password)) {
            redisStandaloneConfiguration.setPassword(password);
        }

        if (StringUtils.isNotEmpty(database)) {
            redisStandaloneConfiguration.setDatabase(Integer.parseInt(database));
        }

        JedisConnectionFactory factory = new JedisConnectionFactory(redisStandaloneConfiguration);
        factory.afterPropertiesSet();
        logger.info("=============getStandaloneFactory init end============");
        return factory;
    }

    /**
     * 获取redis集群模式连接
     */
    private JedisConnectionFactory getClusterFactory() {
        logger.info("============getClusterFactory init============");
        if(StringUtils.isEmpty(clusterNodes)){
            throw new CustomException("集群节点不能为空");
        }
        RedisClusterConfiguration configuration = new RedisClusterConfiguration(Lists.newArrayList(clusterNodes));
        if(StringUtils.isNotEmpty(password)) {
            configuration.setPassword(password);
        }
        JedisConnectionFactory factory = new JedisConnectionFactory(configuration, jedisPoolConfig());
        factory.afterPropertiesSet();
        logger.info("=============getClusterFactory init end============");
        return factory;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisSerializer<Object> serializer = redisSerializer();
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(jedisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RedisSerializer<Object> redisSerializer() {
        // 创建JSON序列化器
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 必须设置，否则无法将JSON转化为对象，会转化成Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);
        return serializer;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        // 设置Redis缓存有效期为1天
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer()))
                .entryTtl(Duration.ofDays(1));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }

    @Bean
    public RedisService redisService() {
        return new RedisServiceImpl();
    }

    @Bean
    public RedisWatcherLockService redisWatcherLockService() {
        return new RedisWatcherLockService();
    }

}
